# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: xguo

from typing import Optional
import functools
import logging

import requests
import schedule
import tornado.gen
import tornado.ioloop

from coin.exchange.util.rate_limit import RateLimiter
from coin.base.timestamp import get_timestamp
from coin.exchange.base.rest_order_gateway_base import PrivateExchangeMessage
import coin.flow.topic_map as topic_map

# schedule log is noisy and not really useful, disable it.
schedule.logger.disabled = True


def _job_func_decorator(job_func, msg_type, run_once=False, **kwargs):
  @tornado.gen.coroutine
  def wrapper(self):
    try:
      if not self._rate_limiter.check():
        return
      request_timestamp = get_timestamp()
      response = yield job_func(**kwargs)
      if response.status_code != 200:
        self._logger.error('Invalid status code: %s (%s)', response.status_code, response.content)
        return
      timestamp = get_timestamp()
      msg = PrivateExchangeMessage(
          msg_type=msg_type,
          data=response,
          received_timestamp=timestamp,
          request_params=kwargs,
          request_timestamp=request_timestamp,
      )
      self._publisher.write(timestamp, msg)
    except requests.exceptions.RequestException:
      self._logger.exception('RequestException')
    except Exception:
      self._logger.exception('Unknown Exception')
    if run_once:
      return schedule.CancelJob

  wrapper.__name__ = job_func.__name__
  return wrapper


class FcoinRestQueryer(object):
  def __init__(self, *, queue, worker_id: str, ioloop=None, logger=None):
    topic_id = topic_map.search_topic_id('fcoin_rest_private_%s' % worker_id)[0][1]
    self._publisher = queue.get_writer(topic_id=topic_id)
    self._scheduler = schedule.Scheduler()
    self._periodic_callback = None
    self._ioloop = ioloop or tornado.ioloop.IOLoop.current()
    self._rate_limiter = RateLimiter(80, 10.)
    self._logger = logger or logging.getLogger(__name__)

  def __del__(self):
    self.stop()
    self._publisher.close()

  def add_periodic_query(self, job_func, job_name, msg_type, query_period, **kwargs):
    job_func = _job_func_decorator(job_func, msg_type, **kwargs)
    job_func = functools.partial(job_func, self)
    self._scheduler.every(query_period).to(query_period + 1).seconds.do(job_func).tag(job_name)

  def remove_periodic_query(self, job_name):
    job = None
    for job in self._scheduler.jobs:
      if job_name in job.tags:
        break
    if job is None:
      self._logger.error('Query job %s not found!', job_name)
    else:
      self._scheduler.cancel_job(job)

  def query_once(self, job_func, msg_type, delay, **kwargs):
    job_func = _job_func_decorator(job_func, msg_type=msg_type, **kwargs)
    self._ioloop.call_later(delay, job_func, self)

  def start(self, later_td: Optional[float] = 0.0):
    self._periodic_callback = tornado.ioloop.PeriodicCallback(self._scheduler.run_pending, 500)
    self._ioloop.call_later(later_td, self._periodic_callback.start)

  def stop(self):
    if self._periodic_callback is not None:
      self._periodic_callback.stop()
